5.3 型の変換
型の変換
変数を宣言する際に、型も宣言しなければならないことを解説してきました。
そして、main関数を用意し、関数の先頭行から型名 + 変数名で宣言することをお伝えしました。
宣言時の型種類のまま、最後の処理まで遂行できれば良いのですが、プログラムが複雑且つ容量が大きくなると、処理の途中で違う型に変換したいケースが出てくることがあります。
参考書と似ているものの少し異なる事例で解説したいと思います。
int main(void)
{
/* 少数を扱う変数と整数を扱う変数を一つずつ宣言した */
float syosu;
int seisu;
syosu = 1.05;
seisu = 0;
/* ここではseisuに 0 が格納されている */
printf("ここでは = %d\n",seisu);
seisu = (int)(syosu);
/* ここではseisuに 1 が格納されている */
printf("ここでは = %d\n",seisu);
return 0;
}
syosuという名の変数には、7行目で1.05が代入されています。
seisuという名の変数には、8行目で0が代入されています。
13行目でseisuにsyosuの値を代入しています。に要に
この時、int型にfloat型の値を代入しようとしているのですが、以下の様に記載するとコンパイラはワーニングを出します。
seisu = syosu;
コンパイルエラーは必ず是正が必要です。
ワーニングは是正しなくとも動作してくれるものが大半ですが、開発企業のほとんどはワーニングも是正することを社内規約としている企業が主です。
どうしても是正できないワーニングとして、開発環境のライセンスの期限勧告などがあります。
物理的に是正できないワーニングを除いて、基本的には修正対応を行うことを推奨いたします。
話を戻しますと、ワーニングの内容としては、「宣言された型が違うものに代入しようとしている」といった内容で英文にて出力されることが多いです。
つまり、変数同士で代入処理をする場合、同じ型種類のもの同士は代入できる。がルールになります。
また、型種類が異なるものに代入したい場合、以下の書き方をすることが決まりになっています。
これをキャストと言います。
代入される変数 = (代入される変数の型種類)(代入する変数)
文字だと分かりづらいので、上記の例で解説します。
seisuはint型で宣言されていました。
syosuはfloat型で宣言されていました。
int型 = float型 をしようとしているのですが、代入されるseisuの型種類をカッコ () で括って、
代入しようとしているsyosuの前に記載します。
seisu = (int)(syosu);
syosuもカッコ()で括っているのは、変数だけではないケースがあるので、思想としてこの様に記載する癖をつけていることに起因します。
例えば、(int)(syosu + 10)といった演算処理結果を変換することもできるのですが、
演算処理が長くなってくると、どこまでが(int)で変換されるのだろうか?と些細な迷いが出てきたりします。
故に、カッコ()で括っておくと、カッコ()の中身は全て変換されるのだ!と明示され可読する方が迷わなくなりますので、その様にしています。
さて、コンパイルエラーではなくワーニングとなるのは何故でしょうか?
これは、代入された処理自体はコンパイル自体に不都合はないからです。
上記のプログラムの例で申し上げると、1.05が1となって格納されても、
1.05が1.05で格納されてもコンパイラは困りません。
また、意図して代入処理をするようなプログラマーも要ます(設計思想としてはお勧めしません)
seisu = syosu;
1.05が1となって代入されることを分かった上で、この様に書く方もいるということです。
(int)(syosu)と書くのが面倒なのか、自分だけが分かるコードであれば良いという思想なのか、
稀に、その様な書き方の設計者が居たりします。
かなり間違います!! 小数計算の注意点
少し難しい話になるかもしれませんが、小数を扱う話題になっているので言及しておきたいと思います。
以下のコードは同じ処理に見えますが、seisuに入る結果が異なります。
int main(void)
{
/* 少数を扱う変数と整数を扱う変数を一つずつ宣言した */
float syosu;
int seisu;
syosu = 1.05;
seisu = 0;
seisu = (int)(syosu * 360);
/* seisuには 377 が格納されている */
printf("ここでは = %d\n",seisu);
seisu = (int)(1.05 * 360);
/* seisuには 378 が格納されている */
printf("ここでは = %d\n",seisu);
return 0;
}
syosuには1.05が代入されています。
変数の1.05と少数値で記載した1.05で演算結果が変わります。
syosu * 360 = 377
1.05 * 360 = 368
コンピューター(マイコン含む)の内部では、syosuの1.05は1.05fとして扱われます。
数値記載の1.05は、そのまま1.05として扱われます。
1.05fとして処理される方は、循環小数となり2進数に変換すると、
1.0001100110011… となり、1.05ではありません。
float型としてsyosuを宣言していますので、約7桁が有効桁範囲となり、
実際には、1.0499999523162841796875が格納されます。
なので、1.0499999523162841796875 * 360 = 377.999982…となり、
小数点以下は切り捨てされて377になります。
直接数値で記載した1.05ですが、C言語では浮動小数点リテラルはdouble型として処理されます。
double型の有効桁範囲は約15桁なので、実際の数値は、1.05000000…となり、
1.05000000… * 360 = 378…となります。
小数点以下が切り捨てされても378に変わりませんので、電卓で計算した結果と同じ値になるわけです。
精度の必要な演算に使う値である場合は、float型ではなくdouble型として宣言をしておく方がベターです。
但し、これまでの章でも出てきた通り、メモリ容量の兼ね合いが出てきますので、盲目的にdouble型で宣言をしていると、ある時点からメモリ不足に悩まされて設計できなくなってしまうので、明確な設計理由がなければ、曖昧になってしまうことがバグへと繋がってしまうことになります。